/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/math.hpp>
#include <baja/graphics.hpp>
#include <baja/observable.hpp>
#include <journal/views/interfaces.h>

namespace journal {  namespace view {

namespace b = baja;
namespace a = baja::app;
namespace m = baja::math;
namespace g = baja::graphics;

class camera : public icamera, public std::enable_shared_from_this<camera>
{
public:
    camera() :
        _dirty(true), 
        _matrixDirty(true),
        _angles(0.f, 0.f, 0.f), 
        _offsetXY(0.f, 0.f), 
        _offsetDistance(0.f),
        _focus(0.f, 0.f),
        _changeNumber(0)
    {}
    static std::shared_ptr<camera> create()
    {
        std::shared_ptr<camera> returnValue = std::make_shared<camera>();
        return returnValue->shared_from_this();
    }

    void apply(const std::shared_ptr<g::iengine>& graphics);

    const m::vector3d& angles() { return _angles; }
    const m::vector2d& offsetXY() { return _offsetXY; }
    const b::float32 offsetDistance() { return _offsetDistance; }
    const m::vector2d& focus() { return _focus; }
    const b::uint64 changeNumber() { return _changeNumber; }
    const b::float32 scale() { return _scale; }
    const m::vector3d& direction() 
    { 
        this->ensureMatrixUpdated();
        return _direction; 
    }
    const m::point3d& position() 
    { 
        this->ensureMatrixUpdated();
        return _position; 
    }
    const m::matrix44& camera::matrix()
    {
        this->ensureMatrixUpdated();
        return _matrix;
    }

    void refresh(const std::shared_ptr<g::iengine>& graphics)
    {
        this->setFocus(this->focus());
        this->setAngles(this->angles());
        this->setOffsetDistance(this->offsetDistance());
        this->setOffsetXY(this->offsetXY());

        this->apply(graphics);
    }

    void setScale(const b::float32 scale);
    void setAngles(const m::vector3d& angles);
    void setOffsetXY(const m::vector2d& offsetXY);
    void setOffsetDistance(const b::float32 offsetDistance);
    void setFocus(const m::vector2d& focus);

    b::int32 addObserver(const std::shared_ptr<icamera_observer>& observer) { return _observerSource += observer; }
    void removeObserver(const std::shared_ptr<icamera_observer>& observer) { _observerSource -= observer; }
    void removeObserver(const b::int32 cookie) { _observerSource -= cookie; }

private:
    void ensureMatrixUpdated();

    b::observable_source<icamera_observer> _observerSource;
    m::matrix44 _matrix;

    m::vector3d _angles;
    m::vector2d _offsetXY;
    m::vector2d _focus;
    b::float32 _scale;
    b::float32 _offsetDistance;
    m::vector3d _direction;
    m::point3d _position;

    bool _dirty;
    bool _matrixDirty;
    b::uint64 _changeNumber;
};

}} // namespace journal::view